package com.git.hui.yinshua.biz.pay.service;

import com.alibaba.fastjson.JSONObject;
import com.git.hui.yinshua.api.model.ExceptionUtil;
import com.git.hui.yinshua.api.model.Status;
import com.git.hui.yinshua.api.model.order.OrderStatusEnum;
import com.git.hui.yinshua.api.model.order.PayStatusEnum;
import com.git.hui.yinshua.api.model.order.RefundStatusEnum;
import com.git.hui.yinshua.biz.order.dao.entity.OrderEntity;
import com.git.hui.yinshua.biz.order.service.OrderService;
import com.git.hui.yinshua.biz.pay.dao.entity.OrderPaymentEntity;
import com.git.hui.yinshua.biz.pay.dao.entity.OrderRefundEntity;
import com.git.hui.yinshua.biz.pay.dao.repository.OrderPaymentRepository;
import com.git.hui.yinshua.biz.pay.service.convert.PaymentConvert;
import com.git.hui.yinshua.biz.pay.service.wx.IWxPayService;
import com.git.hui.yinshua.biz.rest.models.order.rsp.RefundDto;
import com.git.hui.yinshua.common.DateUtil;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 支付服务
 *
 * @author YiHui
 * @date 2024/9/9
 */
@Slf4j
@Service
public class RefundService {
    @Autowired
    private OrderService orderService;
    @Autowired
    private OrderPaymentRepository orderPaymentRepository;
    @Autowired
    private IWxPayService wxPayService;

    /**
     * 查询订单的退款信息
     *
     * @param orderId
     * @return
     */
    public RefundDto getRefundInfo(Long orderId) {
        List<OrderRefundEntity> entityList = orderPaymentRepository.batchQueryRefundByOrderId(Collections.singletonList(orderId));
        if (CollectionUtils.isEmpty(entityList)) {
            return null;
        }
        return PaymentConvert.toRefund(entityList.get(0));
    }

    /**
     * 批量查询订单的退款信息
     *
     * @param orderIds
     * @return
     */
    public Map<Long, RefundDto> listRefundInfo(Collection<Long> orderIds) {
        List<OrderRefundEntity> entityList = orderPaymentRepository.batchQueryRefundByOrderId(orderIds);
        if (CollectionUtils.isEmpty(entityList)) {
            return Collections.emptyMap();
        }
        return entityList.stream().map(PaymentConvert::toRefund).collect(Collectors.toMap(RefundDto::getOrderId, s -> s));
    }

    /**
     * 后台发起退款申请
     * 申请退款->更新为退款中 --> 只有支付成功状态的订单才能退款
     *
     * @param orderId
     * @return
     */
    @Transactional
    public boolean refunding(Long orderId, String refundReason) {
        // 将订单的支付状态，更新为退款中
        OrderEntity order = orderService.lockOrder(orderId);
        if (!order.getPayStatus().equals(PayStatusEnum.SUCCEED.getStatus())) {
            throw ExceptionUtil.of(Status.StatusEnum.BIZ_EXCEPTION, "订单未支付成功，不允许退款！订单ID = " + orderId);
        }
        orderService.refundCallback(order.getId(), RefundStatusEnum.REFUNDING);


        // 根据支付单进行全部退款
        OrderPaymentEntity payment = orderPaymentRepository.queryByOrderId(orderId);
        // 生成一个退款单
        OrderRefundEntity refund = orderPaymentRepository.saveRefund(payment, refundReason);
        // 调用微信的退款流程
        // fixme 这里需要考虑微信退款申请成功，但是后面的流程执行失败的场景
        Refund res = wxPayService.refund(refund, payment.getActualAmount(), payment.getUuid());
        refund.setRemark(JSONObject.toJSONString(res));
        refund.setThirdTransCode(res.getTransactionId());
        refund.setWxRefundId(res.getRefundId());
        if (StringUtils.isNotBlank(res.getSuccessTime())) {
            refund.setRefundTime(DateUtil.wxDayToTimestamp(res.getSuccessTime()));
        }
        RefundStatusEnum refundStatus = this.buildRefundStatus(res.getStatus());
        refund.setRefundStatus(refundStatus == null ? null : refundStatus.getStatus());
        // 保存微信返回的退款单信息
        if (orderPaymentRepository.updateRefundInfo(refund)) {
            // 订单退款回调
            return orderService.refundCallback(payment.getOrderId(), refundStatus);
        } else {
            return false;
        }
    }


    /**
     * 后台发起退款申请
     * 申请退款->更新为退款中 --> 只有支付成功状态的订单才能退款
     *
     * @param orderId
     * @return
     */
    @Transactional
    public boolean adminRefunding(Long orderId, String refundReason, String wxRes) {
        // 将订单的支付状态，更新为退款中
        OrderEntity order = orderService.lockOrder(orderId);
        if (!order.getPayStatus().equals(PayStatusEnum.SUCCEED.getStatus())) {
            throw ExceptionUtil.of(Status.StatusEnum.BIZ_EXCEPTION, "订单未支付成功，不允许退款！订单ID = " + orderId);
        }
        orderService.refundCallback(order.getId(), RefundStatusEnum.REFUNDING);


        // 根据支付单进行全部退款
        OrderPaymentEntity payment = orderPaymentRepository.queryByOrderId(orderId);
        // 生成一个退款单
        OrderRefundEntity refund = orderPaymentRepository.saveRefund(payment, refundReason);
        // 调用微信的退款流程
        Refund res = JSONObject.parseObject(wxRes, Refund.class);
        refund.setRemark(JSONObject.toJSONString(res));
        refund.setThirdTransCode(res.getTransactionId());
        refund.setWxRefundId(res.getRefundId());
        if (StringUtils.isNotBlank(res.getSuccessTime())) {
            refund.setRefundTime(DateUtil.wxDayToTimestamp(res.getSuccessTime()));
        }
        RefundStatusEnum refundStatus = this.buildRefundStatus(res.getStatus());
        refund.setRefundStatus(refundStatus == null ? null : refundStatus.getStatus());
        // 保存微信返回的退款单信息
        if (orderPaymentRepository.updateRefundInfo(refund)) {
            // 订单退款回调
            return orderService.refundCallback(payment.getOrderId(), refundStatus);
        } else {
            return false;
        }
    }

    /**
     * 退款回调
     *
     * @return
     */
    public boolean refundCallback(RefundNotification notification) {
        // 查询退款单
        OrderRefundEntity refund = orderPaymentRepository.queryRefundByUuidForUpdate(notification.getOutRefundNo());
        if (Objects.equals(refund.getRefundStatus(), RefundStatusEnum.SUCCEED.getStatus())) {
            // 如果已经是退款成功，则不需要走后续的流程
            log.info("退款单已经是退款完成了！ order={}, payment={}", refund.getOrderId(), refund.getPaymentId());
            return true;
        }

        // 订单加锁，判断下是否已经退款完成了，若是则直接返回；避免并发导致异常
        OrderEntity order = orderService.lockOrder(refund.getOrderId());
        if (order.getPayStatus().equals(PayStatusEnum.REFUNDED.getStatus()) || Objects.equals(order.getOrderStatus(), OrderStatusEnum.REFUND.getStatus())) {
            log.warn("订单已完成退款，无需继续操作! orderId: {}, payment={}", refund.getOrderId(), refund.getPaymentId());
            return true;
        }

        refund.setRemark(JSONObject.toJSONString(notification));
        refund.setThirdTransCode(notification.getTransactionId());
        refund.setWxRefundId(notification.getRefundId());
        if (StringUtils.isNotBlank(notification.getSuccessTime())) {
            refund.setRefundTime(DateUtil.wxDayToTimestamp(notification.getSuccessTime()));
        }
        RefundStatusEnum status = this.buildRefundStatus(notification.getRefundStatus());
        refund.setRefundStatus(status == null ? null : status.getStatus());
        // 保存微信返回的退款单信息
        if (orderPaymentRepository.updateRefundInfo(refund)) {
            // 订单退款回调
            return orderService.refundCallback(refund.getOrderId(), status);
        } else {
            return false;
        }
    }

    private RefundStatusEnum buildRefundStatus(com.wechat.pay.java.service.refund.model.Status status) {
        switch (status) {
            case SUCCESS:
                return RefundStatusEnum.SUCCEED;
            case PROCESSING:
                return RefundStatusEnum.REFUNDING;
            case ABNORMAL:
                return RefundStatusEnum.FAILED;
            case CLOSED:
                return RefundStatusEnum.CLOSE;
            default:
                return null;
        }
    }
}
